package com.k8.common.aop;

import com.k8.common.aop.internal.interceptor.ExposeInvocationInterceptor;
import com.k8.common.aop.internal.interceptor.MethodInterceptor;
import com.k8.common.aop.internal.invocation.MethodInvocation;
import com.k8.common.aop.internal.invocation.ReflectiveMethodInvocation;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.List;

public class JdkAopProxy implements AopProxy {
    private final JdkAdvisedSupport advisedSupport;

    public JdkAopProxy(AdvisedSupport advisedSupport) {
        this.advisedSupport = (JdkAdvisedSupport) advisedSupport;
    }

    @Override
    public Object getProxy() {
        ClassLoader classLoader = advisedSupport.getClassLoader();
        return getProxy(classLoader);
    }

    @Override
    public Object getProxy(ClassLoader classLoader) {
        return buildProxy(this.determineClassLoader(classLoader), false);
    }

    private ClassLoader determineClassLoader(ClassLoader classLoader) {
        if (classLoader == null) {
            return this.getClass().getClassLoader();
        } else {
            if (classLoader.getParent() == null) {
                ClassLoader aopClassLoader = this.getClass().getClassLoader();

                for (ClassLoader aopParent = aopClassLoader.getParent(); aopParent != null; aopParent = aopParent.getParent()) {
                    if (classLoader == aopParent) {
                        return aopClassLoader;
                    }
                }
            }
            return classLoader;
        }
    }

    @Override
    public Class<?> getProxyClass(ClassLoader classLoader) {
        return (Class<?>) buildProxy(classLoader, true);
    }

    private Object buildProxy(ClassLoader classLoader, boolean classOnly) {
        InvocationHandler invocationHandler = new DynamicAdvisedInvocationHandler(advisedSupport);
        Object o = Proxy.newProxyInstance(classLoader, advisedSupport.targetInterfaces, invocationHandler);
        if (classOnly) return o.getClass();
        else return o;
    }


    public static class DynamicAdvisedInvocationHandler implements InvocationHandler {
        AdvisedSupport advisedSupport;

        public DynamicAdvisedInvocationHandler(AdvisedSupport advisedSupport) {
            this.advisedSupport = advisedSupport;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Method method1 = advisedSupport.getTargetClass().getMethod(method.getName(), method.getParameterTypes());
            List<MethodInterceptor> interceptors = advisedSupport.getChain(method1);
            if (interceptors == null || interceptors.isEmpty()) {
                return method1.invoke(this.advisedSupport.getTarget(), args);
            }
            MethodInvocation methodInvocation = new ReflectiveMethodInvocation(proxy, this.advisedSupport.getTarget(), method, args, advisedSupport.getTargetClass(), interceptors, null);
            ExposeInvocationInterceptor interceptor = new ExposeInvocationInterceptor();
            interceptors.add(0, interceptor);
            return methodInvocation.proceed();
        }
    }

}
